﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GameObjects;
using GameObjects.DrawableClasses;
using GameProcess.Actors;
using Microsoft.Xna.Framework;
using Virvar.Net;
using System.Collections.Concurrent;
using GameObjects.MoveBehaviors;
using System.IO;

namespace GameProcess
{
    public enum Owner
    {
        Client,
        Server
    }

    public class GameState
    {
        private static GameState _instance = null;
        public static GameState Instance
        {
            get { return GameState._instance; }
        }

        internal Owner Owner;
        static int _curCharId = 1;

        public Level Level { get; set; } // уровень
        List<IActor> Actors { get; set; } // агенты, отвечающие за свою область
        List<IPacketHandler> packetHandlers { get; set; } // агенты, обрабатывающие сообщения
        public RealCharacter Player { get; set; } // (клиент)
        public ConcurrentDictionary<int, RealCharacter> Players { get; set; } // игроки (сервер), игроки + боты (клиент)
        public HashSet<RealCharacter> Bots { get; set; } // боты (сервер)
        private LinkedList<GameMoment> _moments = new LinkedList<GameMoment>();
        private int _momentsLength = 30;
        public ConcurrentDictionary<int, Score> Scores = new ConcurrentDictionary<int, Score>(); // таблица очков
        internal Grid Grid = new Grid(); // сетка на карте

        public ConcurrentDictionary<byte, NetPlayer> NetPlayers; // подкюченные игроки (сервер)
        public NetPlayer NetPlayer; // объект, отчечающий за передачу пакетов (клиент)

        public event Action<RealCharacter> CharacterAdded;

        // actors
        ConnectionsCheckActor _connectionsCheckerActor;

        public static void CreateInstance(Owner owner, NetPlayer netPlayer = null)
        {
            if (_instance != null)
                throw new Exception("Одиночке уже присвоено значение");
            _instance = new GameState(owner, netPlayer);
        }
        /// <summary>
        /// Инициализирует экземпляр GameState.
        /// </summary>
        /// <param name="owner">Владелец.</param>
        /// <param name="netPlayer">NetPlayer, если клиент</param>
        private GameState(Owner owner, NetPlayer netPlayer = null)
        {
            this.Owner = owner;
            if (owner == GameProcess.Owner.Server)
            {
                string file;
                using (StreamReader sr = new StreamReader("Maps\\Map.txt"))
                {
                    file = "Maps\\" + sr.ReadLine();
                }
                if (!file.Contains('.'))
                {
                    file = file + ".sam";
                }
                ParseLevel(file);
                Console.WriteLine("Map loaded.");
                Grid.SetLevel(Level);
            }
            Actors = new List<IActor>();
            CollisionActor collisionsActor = new CollisionActor(this);
            MovesActor movesActor = new MovesActor(this, collisionsActor);
            RotationsActor rotationsActor = new RotationsActor(this);
            HitsActor hitsActor = new HitsActor(this);
            ScoresActor scoresActor = new ScoresActor(this);
            ConsoleCommandsActor consoleCommandsActor = new ConsoleCommandsActor(this);
            Actors.Add(movesActor);
            Actors.Add(rotationsActor);
            Actors.Add(hitsActor);
            Actors.Add(scoresActor);
            packetHandlers = new List<IPacketHandler>();
            _connectionsCheckerActor = new ConnectionsCheckActor(this);
            packetHandlers.Add(_connectionsCheckerActor); // обязательно должен быть первым
            packetHandlers.Add(movesActor);
            packetHandlers.Add(new HealthActor(this));
            packetHandlers.Add(rotationsActor);
            packetHandlers.Add(hitsActor);
            packetHandlers.Add(scoresActor);
            packetHandlers.Add(consoleCommandsActor);

            Players = new ConcurrentDictionary<int, RealCharacter>();
            Bots = new HashSet<RealCharacter>();

            if (owner == GameProcess.Owner.Server)
            {
                NetPlayers = new ConcurrentDictionary<byte, NetPlayer>();
            }
            else
            {
                this.NetPlayer = netPlayer;
            }
        }

        public void SetLevel(Level level)
        {
            this.Level = level;
            Grid.SetLevel(level);
        }

        private void ParseLevel()
        {
            //Level = new Level(300, 300);
            //DrawableSolid solid = new DrawableSolid();
            //solid.Size = new Point(50, 100);
            //solid.Position = new Vector2(100, 100);
            //solid.Angle = 3f;
            ////solid.CollisionOffset = solid.Size;
            //Level.Solids.Add(solid);

            Level = new Level(1200, 1200);
            Random rand = new Random();
            for (int i = 0; i < 30; i++)
            {
                DrawableSolid solid = new DrawableSolid();
                solid.Size = new Point(40 * rand.Next(1, 5), 60 * rand.Next(1, 5));
                solid.Position = new Vector2(50 * rand.Next(23), 100 + 50 * rand.Next(21));
                solid.Angle = (float)(rand.NextDouble() * MathHelper.Pi);
                Level.Solids.Add(solid);
            }
        }

        private void ParseLevel(string name)
        {
            using (StreamReader sr = new StreamReader(name))
            {
                string line;
                line = sr.ReadLine();
                int width = Int32.Parse(line.Split('=')[1]);
                line = sr.ReadLine();
                int height = Int32.Parse(line.Split('=')[1]);
                Level = new Level(width, height);
                while ((line = sr.ReadLine()) != null)
                {
                    // точки респауна
                    if ((line = sr.ReadLine()).StartsWith("[RespawnPoint]="))
                    {
                        string[] strs = line.Split('=', ';');
                        int x = int.Parse(strs[1]);
                        int y = int.Parse(strs[2]);
                        Level.RespawnPoints.Add(new Vector2(x, y));
                    }
                    // объекты
                    if (line == "[Solid]")
                    {
                        DrawableSolid solid = new DrawableSolid();
                        line = sr.ReadLine();
                        int w = Int32.Parse(line.Split('=')[1]);
                        line = sr.ReadLine();
                        int h = Int32.Parse(line.Split('=')[1]);
                        solid.Size = new Point(w, h);
                        line = sr.ReadLine();
                        int pX = Int32.Parse(line.Split('=')[1]);
                        line = sr.ReadLine();
                        int pY = Int32.Parse(line.Split('=')[1]);
                        solid.Position = new Vector2(pX, pY);
                        line = sr.ReadLine();
                        solid.Angle = float.Parse(line.Split('=')[1]);
                        Level.Solids.Add(solid);
                    }
                }
                if (Level.RespawnPoints.Count == 0)
                {
                    Level.CreateDefaultRespawnPositions();
                }
            }
        }
        /// <summary>
        /// Создаём персонажа (для сервера).
        /// </summary>
        /// <param name="isPlayer">true, если игрок, false, если бот</param>
        /// <returns></returns>
        public RealCharacter CreateCharacter(bool isPlayer)
        {
            RealCharacter character = new RealCharacter(_curCharId++);
            Scores.TryAdd((byte)character.Id, new Score());
            character.Health = Character.MaxHealth;
            character.HealthRegen = 1;
            character.Speed = 400;
            character.DamagePerSecond = 120;
            character.Mana = Character.MaxMana;
            character.ManaRegen = 10;
            character.Position = Level.GetRespawnPosition();
            if (isPlayer)
            {
                character.MoveBehavior = new EmptyBehavior(character);
                Players.TryAdd(character.Id, character);
                lock (Bots)
                {
                    foreach (Character monster in Bots)
                    {
                        if (monster.MoveBehavior is StupidMonsterMove)
                            (monster.MoveBehavior as StupidMonsterMove).AddTarget(character);
                    }
                }
            }
            else
            {
                lock (Bots)
                {
                    Bots.Add(character);
                    foreach (Character player in Players.Values)
                    {
                        if (character.MoveBehavior is StupidMonsterMove)
                            (character.MoveBehavior as StupidMonsterMove).AddTarget(player);
                    }
                }
            }
            CharacterAdded(character);
            // отправляем клиентам уведомления о том, что новый игрок подключился
            foreach (var netplayer in NetPlayers.Values)
            {
                netplayer.ServerPacket.PlayersConnections.TryAdd((byte)character.Id, ConnectionState.Connected);
            }
            return character;
        }

        public void AddNetPlayer(NetPlayer netPlayer)
        {
            NetPlayers.TryAdd(netPlayer.PlayerId, netPlayer);
            _connectionsCheckerActor.AddPlayersListToPacket(netPlayer);
        }
        /// <summary>
        /// Создаём персонажа (для клиента). Создаётся событие CharacterAdded.
        /// </summary>
        /// <param name="playerId">Id персонажа.</param>
        public void CreateCharacter(int playerId)
        {
            RealCharacter character = new RealCharacter(playerId);
            character.MoveBehavior = new EmptyBehavior(character);
            Players.TryAdd(character.Id, character);
            CharacterAdded(character);
        }

        public void RemoveCharacter(int playerId)
        {
            lock (Bots)
            {
                foreach (Character monster in Bots)
                {
                    if (monster.MoveBehavior is StupidMonsterMove)
                        (monster.MoveBehavior as StupidMonsterMove).RemoveTarget(Players[playerId]);
                }
            }
            RealCharacter removedCharacter;
            Players.TryRemove(playerId, out removedCharacter);
            if (Owner == GameProcess.Owner.Server)
            {
                NetPlayer removedPlayer;
                NetPlayers.TryRemove((byte)playerId, out removedPlayer);
                Score removedScore;
                Scores.TryRemove(playerId, out removedScore);
                _connectionsCheckerActor.SetPlayerDisconnected((byte)playerId);
            }
        }
        // обновление
        public void Update(GameTime gameTime)
        {
            if (Owner == GameProcess.Owner.Server)
            {
                foreach (var actor in Actors)
                {
                    actor.UpdateServer(gameTime);
                }
                // создаём "слепок"
                GameMoment moment = new GameMoment(gameTime, Players.Count + Bots.Count);
                moment.Add(Players.Values);
                moment.Add(Bots);
                _moments.AddLast(moment);
                while (_moments.Count > _momentsLength)
                {
                    _moments.RemoveFirst();
                }
            }
            else if (Owner == GameProcess.Owner.Client)
            {
                foreach (var actor in Actors)
                {
                    actor.UpdateClient(gameTime);
                }
                foreach (var player in Players.Values)
                {
                    player.Update(gameTime);
                }
            }
        }
        /// <summary>
        /// Передаёт пакет всем слушателям сообщений.
        /// </summary>
        /// <param name="data">Пришедшие данные.</param>
        public Packet Parse(byte[] data)
        {
            //Console.WriteLine("Packet length to {0}: {1}", Owner, data.Length);
            if (data == null)
                return null;
            // сервер
            if (Owner == GameProcess.Owner.Server)
            {
                ClientPacket pack = new ClientPacket();
                pack.Parse(data);
                if (!NetPlayers.ContainsKey(pack.PlayerId))
                    return pack;
                // игнорируем пакет, если старый
                if (pack.Sequence < NetPlayers[pack.PlayerId].ServerPacket.Ack)
                    return pack;
                foreach (var handler in packetHandlers)
                {
                    handler.ReceiveServer(pack);
                }
                NetPlayers[pack.PlayerId].ServerPacket.Ack = pack.Sequence;
                return pack;
            }
            // клиент
            else
            {
                ServerPacket pack = new ServerPacket();
                pack.Parse(data);
                // игнорируем пакет, если старый
                if (pack.Sequence < NetPlayer.ClientPacket.Ack)
                    return pack;
                foreach (var handler in packetHandlers)
                {
                    handler.ReceiveClient(pack);
                }
                NetPlayer.ClientPacket.Ack = pack.Sequence;
                return pack;
            }
        }
        /// <summary>
        /// Отправляет данные клиенту\серверу.
        /// </summary>
        public void Send()
        {
            if (Owner == GameProcess.Owner.Server)
            {
                // отправляем клиентам
                foreach (var netplayer in NetPlayers.Values)
                {
                    netplayer.SendToClient(netplayer.ServerPacket.Build());
                    //netplayer.ServerPacket.Clear();
                }
            }
            else
            {
                // отправляем серверу
                NetPlayer.SendToServer(NetPlayer.ClientPacket.Build());
            }
        }

        public void Stop()
        {
            // надо проверить, выполняется ли когда-нибудь это условие
            if (NetPlayers != null)
                NetPlayers.Values.ToList().ForEach(p => p.Close());
        }

        ~GameState()
        {
            Stop();
        }
    }
}
